#include "sr2.hpp"
#include "glz.hpp"
#include "rolz.hpp"

#include "byte_model.hpp"
#include "qsmodel.hpp"

#define GVL_PROFILE 1

#include <gvl/support/profile.hpp>
#include <gvl/zlib/zlib.h>

#include <gvl/math/ieee.hpp>

#include <iostream>

void readfile(std::vector<U8>& dest, char const* path, int max = 0x7fffffff)
{
	FILE* f = fopen(path, "rb");

	int const bufferSize = 4096;
	char buf[4096];

	while (true)
	{
		int amount = (max < bufferSize ? max : bufferSize);
		std::size_t count = fread(buf, 1, amount, f);
		dest.insert(dest.end(), buf, buf + count);
		max -= count;
		if(count < amount || max <= 0)
			break;
	}
}

void writefile(std::vector<U8> const& src, char const* path)
{
	FILE* f = fopen(path, "wb");
	fwrite(&src[0], 1, src.size(), f);
	fclose(f);
}

struct VectorWriter : ByteWriter
{
	std::vector<U8>& vec;

	VectorWriter(std::vector<U8>& vec)
	: vec(vec)
	{
		user = this;
		overflow = overflowFunc;

		next = buf;
		end = buf + 65536;
	}

	static void overflowFunc(ByteWriter* self, int b)
	{
		VectorWriter& w = *static_cast<VectorWriter*>(self->user);
		
		w.vec.insert(w.vec.end(), w.buf, self->next);

		self->next = w.buf;
		self->end = w.buf + 65536;

		if(b >= 0)
			*self->next++ = b;
	}

	unsigned char buf[65536];
};

struct VectorReader : ByteReader
{
	std::vector<U8> const& vec;

	VectorReader(std::vector<U8> const& vec)
	: vec(vec)
	{
		user = this;
		underflow = underflowFunc;

		next = &vec[0];
		end = next + vec.size();
	}

	static bool underflowFunc(ByteReader* self)
	{
		return false; // No more
	}
};

template<typename Model>
void test_bytemodel(ByteReader in, ByteWriter out)
{
	Model m;
	Coder1 e(out);

	while (!in.empty())
	{
		int b = in.get();
		m.encode(e, b);
	}
}

void show_result(std::vector<U8>& uncompressed, std::vector<U8>& compressed, char const* name)
{
	int orgsize = uncompressed.size();
	int newsize = compressed.size();

	printf("%s: %d -> %d, %f bpb\n", name, orgsize, newsize, newsize * 8.0 / orgsize);
}

void test()
{
	

	BitModelApproxCounter::init();

	//int t = (1<<20);
	U32 t = (1<<31);

	int y = 1;
	int prob = 2048;

	U32 prev = 1;

	while(t != prev)
	{
		prev = t;
		int n = t&127, p = t>>9;  // count, prediction
		if (n < 127) ++t;
		//t += ((y<<23) - p)*BitModelApproxCounter::dt[n] & 0xffffff80;
		//t += ((y<<23) - p)*(512/(n+2)) & 0xffffff80;

		int maxp = 8388608 - (1<<11);

		int adjust = (1<<11);

		unsigned int mask = 0 - y;
		t += (((maxp - adjust) & mask) - p)*BitModelApproxCounter::dt[n] & 0xffffff80;

		/*
		int const shift1 = 5;
		int const correction1 = (1<<(shift1)) - 2;
		prob += int(1 + ((4095 + correction1) & mask) - prob) >> shift1;*/
	}

	t = t;
	
	// p = (1<<20);
	// (0 - (1<<20))*256 = -(1<<20)*256 = -(1<<28)
}

void printdbl(double x)
{
	union
	{
		double d;
		uint64 i;
	} v;

	v.d = x;
	//printf("%08x%08x", uint32(v.i >> 32), uint32(v.i & 0xffffffff));
	printf("(%08x, %08x)", uint32(v.i & 0xffffffff), uint32(v.i >> 32));
}

void test2()
{
	//Coder1 c;

	#if 0
		gvl_init_ieee();

		//ASSERT("intermediates are double precision #1", gS(gA(1.0, -11562242321027462.), 1642019876357220.2) == -13204262197384680.);
		//ASSERT("intermediates are double precision #2", gS(gA(1.0, -6.4758762313609902e-006), 4.6367248808744297e-005) == 0.99994715687495983);
		//ASSERT("intermediates are double precision #3", gS(gA(1.0, -0.025106293868442715), -5.4931982728117911e-015) == 0.97489370613156268);

		/*
		// These fail with round-double-53(round-extended-53(x * y))
		ASSERT("multiplication is done with double precision #1", gM(8.5782035936114600e-0256, 9.6044770612653327e-0055) == 8.2389159641705354e-0310);
		ASSERT("multiplication is done with double precision #2", gM(1.5033255271664106e-0171, 7.6121384488969229e-0138) == 1.1443522046551671e-0308);
		ASSERT("multiplication is done with double precision #3", gM(1.7912119214492215e-0236, 1.0043439961829961e-0072) == 1.7989929391989340e-0308);

		// These fail with round-double-53(round-extended-53(x / y))
		ASSERT("division is done with double precision #1", gD(9.9174847050126468e-0179, 6.5505123943214619e+0129) == 1.5140013647802517e-0308);

		ASSERT("double overflow with multiply", gD(gM(1.7e308, 2.0), 2.0) == std::numeric_limits<double>::infinity());

		*/

		/*
		gSqrt(7.9975425458479251e-0252) == 2.8279926707556945e-0126
		*/

		double a = 1.5033255271664106e-0171, b = 7.6121384488969229e-0138;
		double res = 1.1443522046551671e-0308;

		printdbl(a); printf(" * ");
		printdbl(b); printf(" / ");
		printdbl(b); printf(" == ");
		printdbl(res);

		printf("\n");

		printdbl(a * b);

		return;
	#endif

	std::vector<U8> data;
	std::vector<U8> out;
	std::vector<U8> result;

	out.reserve(100000000/2);
	result.reserve(100000001);
	
	if (0)
	{
		test();
	}

	readfile(data, "enwik8");
	//readfile(data, "book1");
	
	if (0)
	{
		out.clear();

		{
			GVL_PROF_TIMER("sr2");

			sr2_compress(VectorReader(data), VectorWriter(out));
		}

		show_result(data, out, "sr2");
	}

	if (0)
	{
		out.clear();
		result.resize(data.size());
		std::vector<uint8> testout;

		{
			GVL_PROF_TIMER("rolz");

			rolz_compress(&data[0], &data[0] + data.size(), VectorWriter(out), testout);
		}

		//writefile(testout, "hs.bin");

		#if 0
		{
			GVL_PROF_TIMER("rolz (decomp)");

			rolz_decompress(VectorReader(out), &result[0], &result[0] + result.size(), &data[0]);
		}

		for(std::size_t i = 0; i < std::min(data.size(), result.size()); ++i)
		{
			if(data[i] != result[i])
			{
				printf("Error @ %d\n", i);
				break;
			}
		}
		#endif

		show_result(data, out, "rolz");
	}

	if (0)
	{
		{
			GVL_PROF_TIMER("sr2 (decomp)");

			sr2_decompress(VectorReader(out), VectorWriter(result));
		}

		for(std::size_t i = 0; i < std::min(data.size(), result.size()); ++i)
		{
			if(data[i] != result[i])
			{
				printf("Error @ %d\n", i);
				break;
			}
		}

		printf("%d, %d\n", data.size(), result.size());
	}

	if (0)
	{
		out.clear();

		GVL_PROF_TIMER("glz");

		glz_compress(VectorReader(data), VectorWriter(out));

		show_result(data, out, "glz");
	}

	if (0)
	{
		out.clear();

		out.resize(data.size());

		int cut = 0;

		{
			GVL_PROF_TIMER("zlib");

			z_stream str;
			str.zalloc = 0;
			str.zfree = 0;
			str.opaque = 0;

			deflateInit(&str, Z_DEFAULT_COMPRESSION);
			str.avail_in = 0;
			str.avail_out = 0;

			str.next_in = &data[0];
			str.avail_in = uInt(data.size());

			str.next_out = reinterpret_cast<Bytef*>(&out[0]);
			str.avail_out = out.size();

			while(true)
			{
				int ret = deflate(&str, Z_NO_FLUSH);
				if(ret == Z_STREAM_END || str.avail_in == 0)
					break;
			}


			do
			{
			/*
				if (str.avail_out == 0)
				{
					out.insert(out.end(), &buf[0], reinterpret_cast<U8*>(str.next_out));
					str.next_out = reinterpret_cast<Bytef*>(&buf[0]);
					str.avail_out = bufferSize;
				}*/
			}
			while (deflate(&str, Z_FINISH) != Z_STREAM_END);

			//out.insert(out.end(), &buf[0], str.next_out);

			cut = str.avail_out;
		}

		out.resize(out.size() - cut);
		show_result(data, out, "zlib");
	}
	
	if(1)
	{
		VectorWriter w(out);
		Coder3 coder(w);

		{
			QSModel<256> m;
			//ByteModelMultiSymbol m;
			//ByteModelBitwise m;
			GVL_PROF_TIMER("raw, truncated end");

			for(int i = 0; i < data.size(); ++i)
			{
				//coder.encode_tiny1();
				coder.dontFlush();
				//coder.encode_unibits(data[i], 8);
				m.encode(coder, data[i]);
			}

			//coder.encode_huge0();

			coder.flush();
		}

		/*
		while(!out.empty() && out.back() == 0)
			out.pop_back();*/

		if (0)
		{
			QSModel<256> m;
			//ByteModelMultiSymbol m;
			//ByteModelBitwise m;
			VectorReader r(out);
			Decoder3 decoder(r);

			for(int i = 0; i < data.size(); ++i)
			{
#if 0
				if(decoder.mayEnd())
					printf("Premature end!");
#endif
				//assert(decoder.decode_tiny1_huge0() == 1);
				//uint8 v = decoder.decode_unibits(8);
				uint8 v = m.decode(decoder);
				if(v != data[i])
				{
					printf("Error @ %d\n", i);
					break;
				}
			}

#if 0
			if(!decoder.mayEnd())
				printf("Late end!");
#endif

			//assert(decoder.decode_tiny1_huge0() == 0);
		}

		show_result(data, out, "end code");
	}

#if 0
	if(0)
	{
		for(int i = 0; i < (1<<16); i += (1<<16)-1)
		{
			printf("[%d: %d] ", i, historyCounters[i].p());
		}
	}

	if(0)
	{
		{
			out.clear();
			{
				GVL_PROF_TIMER("byte model bitwise");

				test_bytemodel<ByteModelBitwise>(VectorReader(data), VectorWriter(out));
			}

			show_result(data, out, "byte model bitwise");
		}

		{
			out.clear();
			{
				GVL_PROF_TIMER("byte model multi-symbol");

				test_bytemodel<ByteModelMultiSymbol>(VectorReader(data), VectorWriter(out));
			}

			show_result(data, out, "byte model multi-symbol");
		}

		{
			out.clear();
			{
				GVL_PROF_TIMER("byte model naive");

				test_bytemodel<ByteModelNaive<> >(VectorReader(data), VectorWriter(out));
			}

			show_result(data, out, "byte model naive");
		}

		{
			out.clear();
			{
				GVL_PROF_TIMER("byte model span");

				test_bytemodel<ByteModelSpan>(VectorReader(data), VectorWriter(out));
			}

			show_result(data, out, "byte model span");
		}

		{
			out.clear();
			{
				GVL_PROF_TIMER("byte model nibble");

				test_bytemodel<ByteModelNibble>(VectorReader(data), VectorWriter(out));
			}

			show_result(data, out, "byte model nibble");
		}

		{
			out.clear();
			{
				GVL_PROF_TIMER("byte model nibble multi-symbol");

				test_bytemodel<ByteModelNibbleMultiSymbol>(VectorReader(data), VectorWriter(out));
			}

			show_result(data, out, "byte model nibble multi-symbol");
		}

		{
			out.clear();
			{
				GVL_PROF_TIMER("qsmodel");

				test_bytemodel<QSModel<256> >(VectorReader(data), VectorWriter(out));
			}

			show_result(data, out, "qsmodel");
		}
	}
#endif
	gvl::present_profile(std::cout);
}

int main()
{
	test2();
}